<?php
/**
 * Created by PhpStorm.
 * User: air
 * Date: 18-8-25
 * Time: 下午7:23
 */


/**
 * 各个排算法，
 * 冒泡排序性能是最低的，绝对不能采用，在数据量较大时，选择排序和插入排序都不适合
 * 双路快速排序整体最优，三路快速排序针对重复值较多时最适合
 * 堆排序：性能比归并排序好，比快速排序差,在数据完全随机的情况下，和三路快速排序性能相当
 * Class Sort
 */
class Sort
{
    /**
     * 选择排序
     * @param $arr
     * @return array
     */
    static public function selectSort($arr){
        $n=sizeof($arr);
        for ($i=0;$i<$n;$i++){
           $index=$i;
           for ($j=$i+1;$j<$n;$j++){
               if ($arr[$j]<$arr[$index]){
                   $index=$j;
               }
           }
           if($index!=$i){
               self::swap($arr[$i],$arr[$index]);
           }
        }
        return $arr;
    }


    /**
     * 选择排序的一种,每次都做交换,效率大大降低,不推荐
     * @param $arr
     * @return mixed
     */
    static public function selectSort2($arr){
        $n=sizeof($arr);
        for ($i=0;$i<$n;$i++){
            for ($j=$i+1;$j<$n;$j++){
                if ($arr[$j]<$arr[$i]){
                    self::swap($arr[$i],$arr[$j]);
                }
            }
        }
        return $arr;
    }

    /**
     * 插入排序
     * @param $arr
     * @return mixed
     */
    static public function insertSort($arr){
        $n=sizeof($arr);
        for ($i=1;$i<$n;$i++){
            $e=$arr[$i];
            for ($j=$i;$j>0&&$arr[$j-1]>$e;$j--){
                $arr[$j]=$arr[$j-1];
            }
            $arr[$j]=$e;
        }
        return $arr;
    }




    /**
     * 冒泡排序
     * @param $arr
     * @return mixed
     */
    static public function bubbleSort($arr){
        $n=sizeof($arr);
        for ($i=0;$i<$n;$i++){
            for ($j=$n-1;$j>$i;--$j){
                if($arr[$j]<$arr[$j-1]){
                    self::swap($arr[$j],$arr[$j-1]);
                }
            }
        }
        return $arr;
    }


    /**
     * 冒泡排序(优化时间并不明显,反而有时会怎增加赋值运算时间,针对基本排好序的数组,优化明显(还得看运气),否则用处不大)
     * @param $arr
     * @return mixed
     */
    static public function bubbleSort2($arr){
        $n=sizeof($arr);
        $lastSwapPosTemp=0;
        for ($i=0;$i<$n;$i++){
            $lastSwapPos=$lastSwapPosTemp;
            for ($j=$n-1;$j>$lastSwapPos;--$j){
                if($arr[$j]<$arr[$j-1]){
                    self::swap($arr[$j],$arr[$j-1]);
                    $lastSwapPosTemp=$j;               // 记录最后一次交换的位置
                }
            }
        }
        return $arr;
    }


    /**
     * 归并排序
     * @param $arr
     * @return mixed
     */
    static public function mergeSort($arr){
        self::divide($arr,0,sizeof($arr)-1);
        return $arr;
    }

    /**
     * 归并排序,自底向上,性能上比自顶向下的归并排序(mergeSort)要差
     * @param $arr
     * @return mixed
     */
    static public function mergeSortBU($arr){
        $n=sizeof($arr);
        for ($size=1;$size<=$n;$size+=$size){
            for ($i=0;$i+$size<$n;$i+=$size*2){
                if($arr[$i+$size-1]>$arr[$i+$size]){
                    self::merge($arr,$i,$i+$size-1,min($i+$size*2-1,$n-1));
                }
            }
        }
        return $arr;
    }


    /**
     * 快速排序
     * @param $arr
     */
    static public function quickSort($arr){
        self::quick($arr,0,sizeof($arr)-1);
        return $arr;
    }

    /**
     * 双路快速排序,对于数值非常接近的数组非常有效,即使是随机数也是比较快
     * @param $arr
     */
    static public function quickSort2($arr){
        self::quick2($arr,0,sizeof($arr)-1);
        return $arr;
    }


    /**
     * 三路快速排序,对于数值非常接近的数组非常有效,即使是随机数也是比较快
     * @param $arr
     */
    static public function quickSort3($arr){
        self::quick3($arr,0,sizeof($arr)-1);
        return $arr;
    }

    /**
     * 快速排序(利用了自带函数，性能没有参考意义)
     * 利用 php 自带函数  array_merge ,性能更好,但是消耗更多内存,对于大量重复数值的,不宜使用
     * @param $arr
     * @return array
     */
    static public function quickSort4($arr){
        if(count($arr)>1){
            self::swap($arr[0],$arr[mt_rand(0,count($arr)-1)]);
            $k=$arr[0];
            $x=array();
            $y=array();
            $_size=count($arr);
            for($i=1;$i<$_size;$i++){
                if($arr[$i]<=$k){
                    $x[]=$arr[$i];
                }elseif($arr[$i]>$k){
                    $y[]=$arr[$i];
                }
            }
            $x=self::quickSort4($x);
            $y=self::quickSort4($y);
            return array_merge($x,array($k),$y);
        }else{
            return$arr;
        }
    }


    /**
     * 堆排序（小根堆），
     * @param $arr
     * @return array
     */
    static public function heapMinSort($arr){
        self::heapMin($arr);
        $n=sizeof($arr);
        $temp=array();
        for ($i=$n-1;$i>=0;$i--){
            $temp[]=$arr[0];
            self::swap($arr[$i],$arr[0]);
            array_pop($arr);
            self::heapMinAdjust($arr);
        }
        return $temp;
    }


    /**
     * 初始化小根堆
     * @param $arr
     */
    private function heapMin(&$arr){
        $n=sizeof($arr);
        if($n==1){
            return;
        }
        for ($i=1;$i<$n;$i++){
            $j=$i;
            while ($j>0&&$arr[$j]<$arr[(int)(($j+1)/2)-1]){
                self::swap($arr[$j],$arr[(int)(($j+1)/2)-1]);
                $j=(int)(($j+1)/2)-1;
            }
        }
    }


    /**
     * 取出堆顶元素后调整堆，使之重新成为小根堆
     * @param $arr
     */
    private function heapMinAdjust(&$arr){
        $n=sizeof($arr);
        for ($i=0;2*$i+1<$n;){
            if(2*$i+2<$n){ // 左右孩子都有
                if($arr[$i]<$arr[2*$i+1]&&$arr[$i]<$arr[2*$i+2]){
                    break;
                }
                if ($arr[2*$i+1]<$arr[2*$i+2]){
                    if($arr[$i]<$arr[2*$i+1]){
                        break;
                    }else{
                        self::swap($arr[$i],$arr[2*$i+1]);
                        $i=2*$i+1;
                    }
                }else{
                    if($arr[$i]<$arr[2*$i+2]){
                        break;
                    }else{
                        self::swap($arr[$i],$arr[2*$i+2]);
                        $i=2*$i+2;
                    }
                }
            }else{ // 只有左孩子
                if($arr[$i]<$arr[2*$i+1]){
                    break;
                }else{
                    self::swap($arr[$i],$arr[2*$i+1]);
                    $i=2*$i+1;
                }
            }

        }
    }



    /**
     * 快速排序递归
     * @param $arr
     * @param $l
     * @param $r
     */
    private function quick(&$arr,$l,$r){
      if($l>=$r){
          return;
      }
      $p=self::partition($arr,$l,$r);
      self::quick($arr,$l,$p-1);
      self::quick($arr,$p+1,$r);
    }



    /**
     * 快速排序递归(双路快速排序),
     * @param $arr
     * @param $l
     * @param $r
     */
    private function quick2(&$arr,$l,$r){
        if($l>=$r){
            return;
        }
        $p=self::partition2($arr,$l,$r);
        self::quick2($arr,$l,$p-1);
        self::quick2($arr,$p+1,$r);
    }

    /**
     * 快速排序递归(三路快速排序),在有较多重复数值的情况下最合适,但是在完全随机的情况下,性能比双路快速排序慢
     * @param $arr
     * @param $l
     * @param $r
     */
    private function quick3(&$arr,$l,$r){
        if($l>=$r){
            return;
        }
        self::swap($arr[$l],$arr[mt_rand($l,$r)]);
        $e=$arr[$l];

        $lt=$l;
        $gt=$r+1;
        $i=$l+1;
        while ($i<$gt){
            if ($arr[$i]<$e){
                self::swap($arr[$i++],$arr[++$lt]);
            }else if ($arr[$i]>$e){
                self::swap($arr[$i],$arr[--$gt]);
            }else{
                $i++;
            }
        }
        self::swap($arr[$l],$arr[$lt]);
        self::quick3($arr,$l,$lt-1);
        self::quick3($arr,$gt,$r);
    }



    /**
     * 用于快速排序,找去分界点
     * @param $arr
     * @param $l
     * @param $r
     * @return mixed
     */
    private function partition(&$arr,$l,$r){
        self::swap($arr[$l],$arr[mt_rand($l,$r)]);
        $e=$arr[$l];
        $j=$l;
        for ($i=$l+1;$i<=$r;$i++){
            if ($arr[$i]<$e){
                self::swap($arr[$i],$arr[++$j]);
            }
        }
        self::swap($arr[$l],$arr[$j]);
        return $j;
    }

    /**
     * 用于快速排序,找去分界点(双路快速排序)
     * @param $arr
     * @param $l
     * @param $r
     * @return mixed
     */
    private function partition2(&$arr,$l,$r){
        self::swap($arr[$l],$arr[mt_rand($l,$r)]);
        $e=$arr[$l];

        $i=$l+1;
        $j=$r;
        while (true){
            while ($i<=$r && $arr[$i]<$e){
                $i++;
            }
            while ($j>=$l+1 && $arr[$j]>$e){
                $j--;
            }
            if($i>$j){
                break;
            }
            self::swap($arr[$i],$arr[$j]);
            $i++;
            $j--;
        }

        self::swap($arr[$l],$arr[$j]);
        return $j;
    }



    /**
     * 归并排序,分治
     * @param $arr
     * @param $l
     * @param $r
     */
    private function divide(&$arr,$l,$r){
        if($l>=$r){
            return;
        }
        $mid=(int)(($l+$r)/2);
        // 分治过程
        self::divide($arr,$l,$mid);
        self::divide($arr,$mid+1,$r);
        if($arr[$mid]>$arr[$mid+1]){
            self::merge($arr,$l,$mid,$r);
        }
    }

    /**
     * 归并操作
     * @param $arr
     * @param $l
     * @param $mid
     * @param $r
     */
    private function merge(&$arr,$l,$mid,$r){
        $temp=$arr;
        $i=$l;
        $j=$mid+1;
        for ($k=$l;$k<=$r;$k++){
            if($i>$mid){                            // 判断左边界
                $arr[$k]=$temp[$j-$l];
                $j++;
            }else if($j>$r){                        // 判断右边界
                $arr[$k]=$temp[$i-$l];
                $i++;
            }else if($temp[$i-$l]<$temp[$j-$l]){
                $arr[$k]=$temp[$i-$l];
                $i++;
            }else{
                $arr[$k]=$temp[$j-$l];
                $j++;
            }
        }
    }


    /**
     * 交换变量数据
     * @param $a
     * @param $b
     */
    static private function swap(&$a,&$b){
        $temp=$a;
        $a=$b;
        $b=$temp;
    }
}